package wonder.tools;

import java.io.ByteArrayInputStream;
import java.io.File;
import java.io.FileInputStream;
import java.io.FileOutputStream;
import java.io.IOException;
import java.io.InputStream;
import java.io.InputStreamReader;
import java.io.OutputStream;
import java.io.OutputStreamWriter;
import java.io.Reader;
import java.io.Writer;
import java.text.DateFormat;
import java.text.ParseException;
import java.util.Date;

import org.codehaus.jackson.JsonFactory;
import org.codehaus.jackson.JsonGenerationException;
import org.codehaus.jackson.JsonGenerator;
import org.codehaus.jackson.JsonParseException;
import org.codehaus.jackson.JsonParser;
import org.codehaus.jackson.JsonProcessingException;
import org.codehaus.jackson.JsonToken;
import org.codehaus.jackson.Version;
import org.codehaus.jackson.map.DeserializationContext;
import org.codehaus.jackson.map.JsonMappingException;
import org.codehaus.jackson.map.ObjectMapper;
import org.codehaus.jackson.map.SerializerProvider;
import org.codehaus.jackson.map.deser.std.UntypedObjectDeserializer;
import org.codehaus.jackson.map.module.SimpleModule;
import org.codehaus.jackson.map.ser.std.SerializerBase;
import org.codehaus.jackson.type.TypeReference;
import org.slf4j.Logger;

import wonder.tools.DateHelper.DF;

/**
 * <p>JsonHelper class.</p>
 *
 * @author u211354
 * @version $Id: $
 */
public class JsonHelper {
    private static final Logger logger = LoggerHelper.getLogger();
    /** Constant <code>DATE_MARKER="$date://"</code> */
    public static final String DATE_MARKER = "$date://";

    /**
     * <p>loadJson</p>
     *
     * @param file a {@link java.io.File} object.
     * @param <T> a T object.
     * @return a T object.
     * @throws org.codehaus.jackson.JsonParseException if any.
     * @throws java.io.IOException if any.
     */
    public static final <T> T loadJson(File file) throws JsonParseException,
            IOException {
        return loadJson(file, new TypeReference<T>() {});
    }

    /**
     * <p>loadJson</p>
     *
     * @param file a {@link java.io.File} object.
     * @param tr a {@link org.codehaus.jackson.type.TypeReference} object.
     * @param <T> a T object.
     * @return a T object.
     * @throws org.codehaus.jackson.JsonParseException if any.
     * @throws java.io.IOException if any.
     */
    public static final <T> T loadJson(File file, TypeReference<T> tr)
            throws JsonParseException, IOException {
        InputStream in = null;
        try {
            in = new FileInputStream(file);
            return loadJson(in, tr);
        } finally {
            if (in != null) in.close();
        }
    }

    /**
     * <p>loadJson</p>
     *
     * @param str a {@link java.lang.String} object.
     * @param <T> a T object.
     * @return a T object.
     * @throws org.codehaus.jackson.JsonParseException if any.
     * @throws java.io.IOException if any.
     */
    public static final <T> T loadJson(String str) throws JsonParseException,
            IOException {
        return loadJson(str, new TypeReference<T>() {});
    }

    /**
     * <p>loadJson</p>
     *
     * @param df a {@link java.text.DateFormat} object.
     * @param str a {@link java.lang.String} object.
     * @param <T> a T object.
     * @return a T object.
     * @throws org.codehaus.jackson.JsonParseException if any.
     * @throws java.io.IOException if any.
     */
    public static final <T> T loadJson(DateFormat df, String str)
            throws JsonParseException, IOException {
        return loadJson(df, str, new TypeReference<T>() {});
    }

    /**
     * <p>loadJson</p>
     *
     * @param str a {@link java.lang.String} object.
     * @param tr a {@link org.codehaus.jackson.type.TypeReference} object.
     * @param <T> a T object.
     * @return a T object.
     * @throws org.codehaus.jackson.JsonParseException if any.
     * @throws java.io.IOException if any.
     */
    public static final <T> T loadJson(String str, TypeReference<T> tr)
            throws JsonParseException, IOException {
        return loadJson(new ByteArrayInputStream(str.getBytes()), tr);
    }

    /**
     * <p>loadJson</p>
     *
     * @param df a {@link java.text.DateFormat} object.
     * @param str a {@link java.lang.String} object.
     * @param tr a {@link org.codehaus.jackson.type.TypeReference} object.
     * @param <T> a T object.
     * @return a T object.
     * @throws org.codehaus.jackson.JsonParseException if any.
     * @throws java.io.IOException if any.
     */
    public static final <T> T loadJson(DateFormat df, String str,
            TypeReference<T> tr) throws JsonParseException, IOException {
        return loadJson(df, new ByteArrayInputStream(str.getBytes()), tr);
    }

    /**
     * <p>loadJson</p>
     *
     * @param in a {@link java.io.InputStream} object.
     * @param <T> a T object.
     * @return a T object.
     * @throws org.codehaus.jackson.JsonParseException if any.
     * @throws java.io.IOException if any.
     */
    public static final <T> T loadJson(InputStream in)
            throws JsonParseException, IOException {
        return loadJson(in, new TypeReference<T>() {});
    }

    /**
     * <p>loadJson</p>
     *
     * @param in a {@link java.io.InputStream} object.
     * @param tr a {@link org.codehaus.jackson.type.TypeReference} object.
     * @param <T> a T object.
     * @return a T object.
     * @throws org.codehaus.jackson.JsonParseException if any.
     * @throws java.io.IOException if any.
     */
    public static final <T> T loadJson(InputStream in, TypeReference<T> tr)
            throws JsonParseException, IOException {
        return loadJson(new InputStreamReader(in), tr);
    }

    /**
     * <p>loadJson</p>
     *
     * @param df a {@link java.text.DateFormat} object.
     * @param in a {@link java.io.InputStream} object.
     * @param tr a {@link org.codehaus.jackson.type.TypeReference} object.
     * @param <T> a T object.
     * @return a T object.
     * @throws org.codehaus.jackson.JsonParseException if any.
     * @throws java.io.IOException if any.
     */
    public static final <T> T loadJson(DateFormat df, InputStream in,
            TypeReference<T> tr) throws JsonParseException, IOException {
        return loadJson(df, new InputStreamReader(in), tr);
    }

    /**
     * <p>loadJson</p>
     *
     * @param in a {@link java.io.Reader} object.
     * @param <T> a T object.
     * @return a T object.
     * @throws org.codehaus.jackson.JsonParseException if any.
     * @throws java.io.IOException if any.
     */
    public static final <T> T loadJson(Reader in) throws JsonParseException,
            IOException {
        return loadJson(in, new TypeReference<T>() {});
    }

    /**
     * <p>loadJson</p>
     *
     * @param in a {@link java.io.Reader} object.
     * @param tr a {@link org.codehaus.jackson.type.TypeReference} object.
     * @param <T> a T object.
     * @return a T object.
     * @throws org.codehaus.jackson.JsonParseException if any.
     * @throws java.io.IOException if any.
     */
    public static final <T> T loadJson(Reader in, TypeReference<T> tr)
            throws JsonParseException, IOException {
        JsonFactory jf = new JsonFactory();
        jf.enable(JsonParser.Feature.ALLOW_COMMENTS);
        JsonParser jp = jf.createJsonParser(in);

        ObjectMapper jm = new ObjectMapper();
        SimpleModule dateModule = new SimpleModule("DModule", new Version(1, 0,
                0, null));
        dateModule.addDeserializer(Object.class, new DHDateDeserializer());
        jm.registerModule(dateModule);

        return jm.readValue(jp, tr);
    }

    /**
     * <p>loadJson</p>
     *
     * @param df a {@link java.text.DateFormat} object.
     * @param in a {@link java.io.Reader} object.
     * @param tr a {@link org.codehaus.jackson.type.TypeReference} object.
     * @param <T> a T object.
     * @return a T object.
     * @throws org.codehaus.jackson.JsonParseException if any.
     * @throws java.io.IOException if any.
     */
    public static final <T> T loadJson(DateFormat df, Reader in,
            TypeReference<T> tr) throws JsonParseException, IOException {
        JsonFactory jf = new JsonFactory();
        jf.enable(JsonParser.Feature.ALLOW_COMMENTS);
        JsonParser jp = jf.createJsonParser(in);

        ObjectMapper jm = new ObjectMapper();
        jm.setDateFormat(df);
        return jm.readValue(jp, tr);
    }

    /**
     * <p>saveJson</p>
     *
     * @param file a {@link java.io.File} object.
     * @param val a {@link java.lang.Object} object.
     * @throws org.codehaus.jackson.JsonGenerationException if any.
     * @throws org.codehaus.jackson.map.JsonMappingException if any.
     * @throws java.io.IOException if any.
     */
    public static final void saveJson(File file, Object val)
            throws JsonGenerationException, JsonMappingException, IOException {
        OutputStream out = null;
        try {
            out = new FileOutputStream(file);
            saveJson(out, val);
        } finally {
            if (out != null) out.close();
        }
    }

    /**
     * <p>saveJson</p>
     *
     * @param out a {@link java.io.OutputStream} object.
     * @param val a {@link java.lang.Object} object.
     * @throws org.codehaus.jackson.JsonGenerationException if any.
     * @throws org.codehaus.jackson.map.JsonMappingException if any.
     * @throws java.io.IOException if any.
     */
    public static final void saveJson(OutputStream out, Object val)
            throws JsonGenerationException, JsonMappingException, IOException {
        saveJson(new OutputStreamWriter(out), val);
    }

    /**
     * <p>saveJson</p>
     *
     * @param out a {@link java.io.Writer} object.
     * @param val a {@link java.lang.Object} object.
     * @throws org.codehaus.jackson.JsonGenerationException if any.
     * @throws org.codehaus.jackson.map.JsonMappingException if any.
     * @throws java.io.IOException if any.
     */
    public static final void saveJson(Writer out, Object val)
            throws JsonGenerationException, JsonMappingException, IOException {
        ObjectMapper jm = new ObjectMapper();
        jm.writeValue(out, val);
    }

    /**
     * <p>toJson</p>
     *
     * @param val a {@link java.lang.Object} object.
     * @return a {@link java.lang.String} object.
     * @throws java.lang.Exception if any.
     */
    public static final String toJson(Object val) throws Exception {
        ObjectMapper jm = new ObjectMapper();
        SimpleModule dateModule = new SimpleModule("SModule", new Version(1, 0,
                0, null));
        dateModule.addSerializer(new DHDateSerializer(Date.class));
        jm.registerModule(dateModule);

        return jm.writeValueAsString(val);
    }

    /**
     * <p>toJson</p>
     *
     * @param df a {@link java.text.DateFormat} object.
     * @param val a {@link java.lang.Object} object.
     * @return a {@link java.lang.String} object.
     * @throws java.lang.Exception if any.
     */
    public static final String toJson(DateFormat df, Object val)
            throws Exception {
        ObjectMapper jm = new ObjectMapper();
        jm.setDateFormat(df);

        return jm.writeValueAsString(val);
    }

    private static class DHDateDeserializer extends UntypedObjectDeserializer {
        private static final Logger logger = LoggerHelper.getLogger();

        protected DHDateDeserializer() {
            super();
        }

        @Override
        public Object deserialize(JsonParser jp, DeserializationContext ctxt)
                throws IOException, JsonProcessingException {
            if (jp.getCurrentToken() == JsonToken.VALUE_STRING) {
                String text = jp.getText();
                if (text.startsWith(DATE_MARKER)) {
                    logger.trace("{}", text);
                    try {
                        return DateHelper.getDateFormat(DF.ISO_8601).parse(
                                text.substring(8));
                    } catch (ParseException e) {
                        throw new IOException("Unable to deserialize " + text,
                                e);
                    }
                }
                return jp.getText();
            }
            return super.deserialize(jp, ctxt);
        }
    }

    private static class DHDateSerializer extends SerializerBase<Date> {

        protected DHDateSerializer(Class<Date> t) {
            super(t);
        }

        @Override
        public void serialize(Date value, JsonGenerator jgen,
                SerializerProvider provider) throws IOException,
                JsonProcessingException {
            String text = DATE_MARKER
                    + DateHelper.getDateFormat(DF.ISO_8601).format(value);

            logger.trace("{}", text);
            jgen.writeString(text);
        }
    }
}
